package auth

import (
	"fmt"

	"github.com/casbin/casbin/v2"
	clientv3 "go.etcd.io/etcd/client/v3"
)

const (
	ErrResultNotOK            = "result not ok"
	ErrAddPoliciesMSG         = "failed to add policies, error: %s"
	ErrRemovePoliciesMSG      = "failed to remove policies, error: %s"
	ErrAddGroupPoliciesMSG    = "failed to add group policies, error: %s"
	ErrRemoveGroupPoliciesMSG = "failed to remove group policies, error: %s"
)

type CasbinEnforce struct {
	Daemon   string
	enforcer *casbin.CachedEnforcer
}

func NewCasbinEnforce(etcdCli *clientv3.Client, c *CasbinConf, daemon string) (*CasbinEnforce, error) {
	adapter := NewEtcdAdapter(etcdCli, c.Prefix, daemon)
	enforcer, err := casbin.NewCachedEnforcer(c.Path, adapter)
	if err != nil {
		return nil, fmt.Errorf("failed to create enforcer for casbin: %w", err)
	}
	watcher, err := NewWatcher(etcdCli, c.Prefix, daemon)
	if err != nil {
		return nil, fmt.Errorf("failed to create watcher for casbin: %w", err)
	}
	watcher.SetUpdateCallback(func(msg string) {
		if loadErr := enforcer.LoadPolicy(); loadErr != nil {
			panic(fmt.Errorf("failed to load casbin policy: %w", loadErr))
		}
	})
	enforcer.SetWatcher(watcher)
	return &CasbinEnforce{enforcer: enforcer, Daemon: daemon}, nil
}

func (c *CasbinEnforce) HasPermission(ptype, url, method string) (bool, error) {
	if ptype == "" {
		return false, nil
	}
	return c.enforcer.Enforce(ptype, c.Daemon, url, method)
}

func (c *CasbinEnforce) AddPolicies(rules [][]string) error {
	if len(rules) > 0 {
		ok, err := c.enforcer.AddPolicies(rules)
		if err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf(ErrAddPoliciesMSG, ErrResultNotOK)
		}
	}
	return nil
}

func (c *CasbinEnforce) RemovePolicies(rules [][]string) error {
	if len(rules) > 0 {
		ok, err := c.enforcer.RemovePolicies(rules)
		if err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf(ErrRemovePoliciesMSG, ErrResultNotOK)
		}
	}
	return nil
}

func (c *CasbinEnforce) AddGroupPolicies(rules [][]string) error {
	if len(rules) > 0 {
		ok, err := c.enforcer.AddGroupingPolicies(rules)
		if err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf(ErrAddGroupPoliciesMSG, ErrResultNotOK)
		}
	}
	return nil
}

func (c *CasbinEnforce) RemoveGroupPolicies(rules [][]string) error {
	if len(rules) > 0 {
		ok, err := c.enforcer.RemoveGroupingPolicies(rules)
		if err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf(ErrRemoveGroupPoliciesMSG, ErrResultNotOK)
		}
	}
	return nil
}

func (c *CasbinEnforce) SavePolicies() error {
	return c.enforcer.SavePolicy()
}

func PolicyMenuSub(appName string, menuId uint32) string {
	return fmt.Sprintf("%s_menu_%d", appName, menuId)
}

func PolicyButtonSub(appName string, buttonId uint32) string {
	return fmt.Sprintf("%s_button_%d", appName, buttonId)
}

func PolicyRoleSub(appName string, roleId uint32) string {
	return fmt.Sprintf("%s_role_%d", appName, roleId)
}
